/* Copyright  2009-2010
   @Author
   Richard Changde Yin            e-mail yinchangde@hotmail.com

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

/**
 * @filename: transaction.cc
 */
#include <server_includes.h>

#include<transaction.h>
#include<lock.h>
#include<database.h>
#include<allocator.h>
#include<catalogtables.h>
#include<debug.h>

#include <error.h>

using namespace BackEnd;

/*
    @parameter in    sysdb Database *
    @parameter in    node  LockhashNode*
    @return DbRetVal:


*/

DbRetVal Transaction::insertIntoHashList(Database *sysdb, LockHashNode *node)
{
    //allocate lock node
    Chunk *chunk = sysdb->getSystemDatabaseChunk(TransHasTableId);
    DbRetVal rv = OK;

    /// Allocate one TransHashNode Object
    TransHashNode *hasNode = (TransHashNode*)chunk->allocate(sysdb, &rv);
    if (NULL == hasNode)
          return rv;

    hasNode->node_ = node;
    hasNode->next_ = NULL;

    if (NULL == hasLockList_)
    {
        hasLockList_    = hasNode;
        tailLockList_   = hasNode;
        return BackEnd::OK;
    }
    tailLockList_->next_ = NULL ;
    tailLockList_  = hasNode ;
    return OK;
}

DbRetVal Transaction::removeFromHashList(Database *sysdb, void *tuple)
{
    Chunk *chunk = sysdb->getSystemDatabaseChunk(TransHasTableId);
    TransHashNode *iter = hasLockList_, *prev = hasLockList_;
    if (NULL == iter)
    {
        return BackEnd::ER_NotFound;
    }
    while (iter != NULL)
    {
        if (tuple == iter->node_->ptrToTuple_)
        {
            prev->next_ = iter->next_;

            /// Free the TransHashNode.
            chunk->free(sysdb, iter);
            /// If prev->next == NULL ;
            /// we should change tailLockList_;
            if ( NULL == prev->next_ )
            {
                tailLockList_ = prev;
            }
            if (iter == hasLockList_)
            {
                hasLockList_  = NULL;
                tailLockList_ = NULL;
            }
            return OK;
        }
        prev = iter;
        iter = iter->next_;
    }
    return BackEnd::ER_NotFound;
}


DbRetVal Transaction::releaseAllLocks(LockManager *lockManager_)
{
    Database *sysdb =lockManager_->getDatabase();
    Chunk *chunk = sysdb->getSystemDatabaseChunk(TransHasTableId);


    TransHashNode *iter  = hasLockList_, *prev;
    while (NULL != iter)
    {
        prev = iter;
        iter = iter->next_;
        lockManager_->releaseLock(prev->node_->ptrToTuple_);
        chunk->free(sysdb, prev);
    }
    tailLockList_=hasLockList_ = NULL;
    return OK;
}
bool Transaction::findInHashList(Database *, LockHashNode *node)
{
    TransHashNode *iter  = hasLockList_;
    while (NULL != iter)
    {
        if (iter->node_ == node) return true;
        iter = iter->next_;
    }
    return false;
}

DbRetVal Transaction::appendUndoLog(Database *sysdb,
                                    OperationType type,
                                    void *data,
                                    size_t size)
{
    DbRetVal rv =OK;
    UndoLogInfo *logInfo = createUndoLog(sysdb, type, data, size, &rv);
    if (logInfo == NULL) return rv;
    memcpy((char*)logInfo + sizeof(UndoLogInfo), data, size);
    addAtBegin(logInfo);
    return OK;
}



DbRetVal Transaction::appendLogicalUndoLog(Database *sysdb,
                                           OperationType type,
                                           void *data,
                                           size_t size,
                                           void* indexPtr)
{
    DbRetVal rv = OK;
    UndoLogInfo *logInfo = createUndoLog(sysdb, type, data, size, &rv);
    if (logInfo == NULL) return rv;
    char **indPtr = (char**)((char*)logInfo + sizeof(UndoLogInfo));
    *indPtr = (char*)  indexPtr;
    addAtBegin(logInfo);
    return rv;
}

DbRetVal Transaction::appendLogicalHashUndoLog(Database *sysdb,
                                               OperationType type,
                                               void *data,
                                               size_t size)
{
    DbRetVal rv = OK;
    HashUndoLogInfo *hInfo = (HashUndoLogInfo *) data;
    UndoLogInfo *logInfo = createUndoLog(sysdb, type, hInfo->tuple_, size, &rv);
    if (!logInfo) return rv;
    memcpy((char*)logInfo + sizeof(UndoLogInfo), data,
               sizeof(HashUndoLogInfo));
    addAtBegin(logInfo);
    return rv;
}

UndoLogInfo* Transaction::createUndoLog(Database *sysdb,
                                        OperationType type,
                                        void *data,
                                        size_t size,
                                        DbRetVal *rv)
{
    Chunk *chunk = sysdb->getSystemDatabaseChunk(UndoLogTableID);
    UndoLogInfo *logInfo = (UndoLogInfo*)chunk->allocate(sysdb,
                                                size + sizeof(UndoLogInfo), rv);
    if (!logInfo) {
        return NULL;
    }
    /// the operation type;
    logInfo->opType_ = type;
    /// the pointer to the Tuple
    logInfo->ptrToTuple_ = data;
    /// the size
    logInfo->size_ = size;
    /// the next_
    logInfo->next_ = NULL;
    return logInfo;
}

void Transaction::addAtBegin(UndoLogInfo* logInfo)
{
    //add it to the begin of the log list
    logInfo->next_ = firstUndoLog_;
    firstUndoLog_ = logInfo;
    return;
}

UndoLogInfo* Transaction::popUndoLog()
{
    UndoLogInfo *iter = firstUndoLog_, *prev = firstUndoLog_;
    if(NULL != iter)
    {
        prev = iter;
        iter = iter->next_;
    }
    firstUndoLog_ = iter;
    return prev;

}

int Transaction::noOfUndoLogs()
{
    UndoLogInfo *iter = firstUndoLog_;
    int count =0;
    while(NULL != iter)
    {
        count++;
        iter = iter->next_;
    }
    return count;
}
void Transaction::printDebugInfo(Database *sysdb)
{
   printf("<TransactionInfo>\n");
   if (waitLock_ != NULL)
   {
       printf("<WaitLock>");
       waitLock_->print();
       printf("</WaitLock>");

   }
   printf("<UndoLogs>\n");
   Chunk *chunk = sysdb->getSystemDatabaseChunk(UndoLogTableID);
   printf("  <TotalPages> %ld </TotalPages>\n",(long)chunk->totalPages());
   UndoLogInfo *iter = firstUndoLog_;
   int count =0;
   while(NULL != iter)
   {
      iter->print();
      iter = iter->next_;
      count++;
   }
   printf("</TotalNodes> %d </TotalNodes>\n", count);
   printf("</UndoLogs>\n");

   printf("<TransHasList>\n");
   chunk = sysdb->getSystemDatabaseChunk(TransHasTableId);
   printf("  <TotalPages> %ld </TotalPages>\n",(long)chunk->totalPages());
   TransHashNode *hasIter  = hasLockList_;
   count =0;
   while (NULL != hasIter)
   {
       hasIter->print();
       hasIter = hasIter->next_;
       count++;
   }
   printf("</TotalNodes> %d </TotalNodes>\n", count);
   printf("</TransHasList>\n");
   printf("</TransactionInfo>\n");
   return ;
}



DbRetVal Transaction::removeUndoLogs(Database *sysdb)
{
    Chunk *chunk = sysdb->getSystemDatabaseChunk(UndoLogTableID);
    UndoLogInfo *logInfo = NULL;
    while(NULL != (logInfo = popUndoLog()))
    {
        chunk->free(sysdb, logInfo);
    }
    return OK;
}


DbRetVal Transaction::applyUndoLogs(Database *sysdb)
{
    Chunk *chunk = sysdb->getSystemDatabaseChunk(UndoLogTableID);

    UndoLogInfo *logInfo = NULL;

    while(NULL != (logInfo = popUndoLog()))
    {
        switch(logInfo->opType_)
        {
                case InsertOperation:
                *((int*)(logInfo->ptrToTuple_) - 1) = 0;
                //May memcpy is not needed as no one will update this
                //as lock is taken on this tuple
                memcpy(logInfo->ptrToTuple_, (char*) logInfo +
                            sizeof(UndoLogInfo), logInfo->size_);
                break;
                case DeleteOperation:
                *((int*)(logInfo->ptrToTuple_) - 1) = 1;
                memcpy(logInfo->ptrToTuple_, (char*) logInfo +
                            sizeof(UndoLogInfo), logInfo->size_);
                break;
                case UpdateOperation:
                memcpy(logInfo->ptrToTuple_, (char*) logInfo +
                            sizeof(UndoLogInfo), logInfo->size_);
                break;

                case InsertHashIndexOperation:
                HashIndex::deleteLogicalUndoLog(sysdb, (char *)logInfo
                                                    + sizeof(UndoLogInfo));
                break;
                //case UpdateHashIndexOperation:
                //HashIndex::updateLogicalUndoLog((char *)logInfo
                //                                    + sizeof(UndoLogInfo));
                //break;
                case DeleteHashIndexOperation:
                HashIndex::insertLogicalUndoLog(sysdb, (char *)logInfo
                                                    + sizeof(UndoLogInfo));
                break;
                default:
                break;
            }
        chunk->free(sysdb, logInfo);
    }
    return OK;
}
